#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/epoll.h>
#include <fcntl.h>

# define SERV_PORT 9527
# define MAX_EVENTS 1024 //监听上限

void sys_err(const char *str)
{
	perror(str);
	exit(1);
}

//描述就绪文件描述符相关信息
struct myevents{
	int fd;                                                //要监听的文件描述符
	int events;                                            //对应的监听事件 
	void *arg;                                             //泛型参数 
	void (*callback) (int fd, int events, void *arg);      //回调函数
	int status;                                            //是否在监听:1->在红黑树上(监听), 0->不在监听
	char buf[1024];
	int len;
	long last_active;                                      //记录每次加入红黑树 g_efd 的时间值
};

int g_efd;                                                  //全局变量, 保存epoll_create返回的文件描述符（红黑树的根结点）
struct myevent_s g_events[MAX_EVENTS+1];                    //自定义结构体类型数组. +1-->listen fd

//运用epoll反应堆模型创建服务器
int main(int argc, char *argv[])
{
	int lfd, cfd;
	int i ,j, fret;
	char buf[4096], clit_ip[1024];

	struct sockaddr_in	serv_addr, clit_addr;
	socklen_t	clit_addr_len;

	int nready, efd, res;
	struct epoll_event tep, ep[1024];//temp:epoll_cnl的参数 ep[1024]:epoll_wait的参数

	bzero(&serv_addr, sizeof(serv_addr));//将地址空间清零，即初始化，也可以用memset函数
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(SERV_PORT);//本地字节序转网络字节序
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);//任意可用ip地址

	lfd = socket(AF_INET,SOCK_STREAM,0);
	if(lfd == -1)
		sys_err("server socket create error");


	//设置端口复用
	int opt = 1;
	int ret = setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR,&opt, sizeof(opt));
	if(ret == -1)
		sys_err("setsocketopt  error");


	ret = bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
	if(ret == -1)
		sys_err("server listen error");

	ret = listen(lfd, 128);
	if(ret == -1){
		sys_err("server listen error");
	}

	efd = epoll_create(1024);//创建epoll红黑树
	if (efd == -1)
		sys_err("epoll create error");

	tep.events = EPOLLIN | EPOLLET;//设置边沿触发
	tep.data.fd = lfd;
	res = epoll_ctl(efd, EPOLL_CTL_ADD, lfd, &tep);//将lfd及对应的结构体挂到树上
	if (res == -1)
		sys_err("epoll_ctl add lfd error");

	while(1){
		nready = epoll_wait(efd, ep, 1024, -1);//epoll_wait阻塞监听
		if (nready == -1)
			sys_err("epoll_wait error");

		for (i = 0; i < nready; i++){

			if(ep[i].data.fd == lfd){

				clit_addr_len = sizeof(clit_addr);//传入只用求一次，放在循环里会反复求
				cfd = accept(lfd, (struct sockaddr *)&clit_addr, &clit_addr_len) ;
				if(cfd == -1)
					sys_err("server accept error");

				printf("client %d connected\n", cfd);
				printf("client ip:%s, port:%d\n", 
						inet_ntop(AF_INET, &clit_addr.sin_addr.s_addr, clit_ip, sizeof(clit_ip)),
						ntohs(clit_addr.sin_port));

				tep.events = EPOLLIN | EPOLLET;//将创建的cfd挂到树上
				tep.data.fd = cfd;

				int flag = fcntl(cfd, F_GETFL);          // 修改cfd为非阻塞读
				flag |= O_NONBLOCK;
				fcntl(cfd, F_SETFL, flag);

				res = epoll_ctl(efd, EPOLL_CTL_ADD, cfd, &tep);//将cfd及对应的结构体挂到树上
				if (res == -1)
					sys_err("epoll_ctl add cfd error");
			}
			else{  //不是lfd的情况

				while((fret = read(ep[i].data.fd , buf ,sizeof(buf))) >= 0){ //需要修改为非阻塞读，并且轮询
					if (fret == 0){
						res = epoll_ctl(efd, EPOLL_CTL_DEL, ep[i].data.fd, NULL);//从epoll树上摘下
						if (res ==-1)
							sys_err("epoll_ctl delete cfd error");

						printf("client %d closed\n", ep[i].data.fd);
						close(ep[i].data.fd);
					}

					write(STDOUT_FILENO, buf ,fret);

					for(j = 0; j<fret; j++){
						buf[j] = toupper(buf[j]);//对数据进行操作			
					}
					write(ep[i].data.fd,buf,fret);
				}
			}
		}
	}

	close(lfd);
	return 0;
}

